from abc import  ABC, abstractmethod




class IByteBuffer(ABC):
    @abstractmethod
    def write_bytes(self, bytes_data):
        pass
    @abstractmethod
    def read_bytes(self, size):
        pass

    def read_all(self):
        return self.read_bytes(self.readable_bytes())


    #return the memoryview of readable data
    @abstractmethod
    def memoryview(self):
        pass

    def skip_all(self):
        return self.skip_bytes(self.readable_bytes())

    @abstractmethod
    def skip_bytes(self, size):
        pass
    @abstractmethod
    def capacity(self):
        pass

    @abstractmethod
    def readable_bytes(self):
        pass
    @abstractmethod
    def writable_bytes(self):
        pass

    @abstractmethod
    def is_readable(self):
        pass
    @abstractmethod
    def is_readable_size(self, size):
        pass

    @abstractmethod
    def is_writable(self):
        pass
    @abstractmethod
    def is_writable_size(self, size):
        pass

    @abstractmethod
    def clear(self):
        pass

    @abstractmethod
    def reader_index(self):
        pass

    @abstractmethod
    def writer_index(self):
        pass

    @abstractmethod
    def _str_internal(self):
        pass



from collections import deque

class ByteBuffer(IByteBuffer):
    DEFAULT_ALIGN_BASE = 16
    ALLOC_CHANGE_LINE = 1024 * 1024
    ALLOC_STEP = 1024

    ALIGN_TO = lambda x, align: (x + (align - 1)) & ~(align - 1)


    @staticmethod
    def calc_alloc_size(size):
        if size < ByteBuffer.ALLOC_CHANGE_LINE:
            return ByteBuffer.ALIGN_TO(size, 1 << size.bit_length()) if size > ByteBuffer.DEFAULT_ALIGN_BASE else ByteBuffer.DEFAULT_ALIGN_BASE

        elif size > ByteBuffer.ALLOC_CHANGE_LINE:
            return ByteBuffer.ALLOC_CHANGE_LINE + ByteBuffer.ALIGN_TO(size - ByteBuffer.ALLOC_CHANGE_LINE, ByteBuffer.ALLOC_STEP)

        else:
            return ByteBuffer.ALLOC_CHANGE_LINE

    def __init__(self, size=DEFAULT_ALIGN_BASE):
        self.__array = bytearray(ByteBuffer.calc_alloc_size(size))
        self.__view = memoryview(self.__array)
        self.__read_index = 0
        self.__write_index = 0
        #print("init memory len:{}".format(len(self)))

    def __move(self, to, index=0):
        readable_len = self.readable_bytes()
        to[index: index+readable_len] = self.__array[self.__read_index:self.__write_index]
        self.__read_index = index
        self.__write_index = index + readable_len

    def ensure_writable(self, size):
        if size <= self.__max_fast_writable_bytes():
            pass
        elif size <= self.writable_bytes():
            #print("move inside array")
            self.__move(self.__array, 0)
        else:  # size > self.writable_bytes(), need to expand array
            #print("move between two array")
            new_space = bytearray(ByteBuffer.calc_alloc_size(size + self.readable_bytes()))
            self.__move(new_space, 0)
            self.__array = new_space
            self.__view = memoryview(self.__array)
            #print("realloc memory len:{}".format(len(self)))

    #return writed_bytes
    def write_bytes(self, bytes_data):
        data_len = len(bytes_data)
        self.ensure_writable(data_len)
        self.__array[self.__write_index : self.__write_index + data_len] = bytes_data
        self.__write_index += data_len
        return data_len

    def read_bytes(self, size):
        size = size if size <= self.readable_bytes() else self.readable_bytes()
        read_start = self.__read_index
        self.__read_index += size
        return self.__array[read_start:self.__read_index]

    
    def memoryview(self):
        assert (self.__read_index <= self.__write_index)
        return self.__view[self.__read_index:self.__write_index]


    # return size of ignored bytes
    def skip_bytes(self, size):
        rbytes = self.readable_bytes()
        size = size if rbytes >= size else rbytes
        self.__read_index += size
        return size

    ###  lengths calculation
    def capacity(self):
        return len(self.__array)

    def readable_bytes(self):
        return self.__write_index - self.__read_index

    # no memroy alloc
    def writable_bytes(self):
        return self.capacity() - self.readable_bytes()

    def is_readable(self):
        return self.__write_index > self.__read_index

    def is_readable_size(self, size):
        return self.is_readable() and self.readable_bytes() >= size

    
    def is_writable(self):
        return True
    def is_writable_size(self, size):
        return True

    def clear(self):
        self.__read_index = self.__write_index = 0

    def reader_index(self):
        return self.__read_index

    def writer_index(self):
        return self.__write_index


    def __is_fast_writable(self):
        return self.capacity() > self.__write_index
    def __is_fast_writable_size(self, size):
        return self.__is_fast_writable() and self.__max_fast_writable_bytes() >= size
    #no memory move & realloc
    def __max_fast_writable_bytes(self):
        return self.capacity() - self.__write_index

    def _str_internal(self):
        return "capacity:{},i_read:{},i_write:{},data-len:{}".format(len(self.__array), self.__read_index, self.__write_index, self.readable_bytes())

    def __str__(self):
        return "ByteBuffer {}".format(self._str_internal())

    def __len__(self):
        return len(self.__array)



"""
    use lock
    with bb.get_lockable() as l:
        writed_bytes = socket.write(bb.source().memoryview())
        l.release()
        l.acquire()
        locked_data.skip_bytes(writed_bytes)
"""
class ILockGuard(ABC):
    def __init__(self, src):
        self.__src = src

    def source(self):
        return self.__src

    def __enter__(self):
        self.acquire()
        return self
    def __exit__(self, *args):
        self.release()

    @abstractmethod
    def acquire(self):
        pass
    @abstractmethod
    def release(self):
        pass

    #nolock version of src
    @abstractmethod
    def memoryview(self):
        pass #

    #nolock version of src
    @abstractmethod
    def skip_bytes(self, size):
        pass

    def skip_all(self):
        return self.skip_bytes(len(self.memoryview()))



class ILockableByteBuffer(IByteBuffer):
    @abstractmethod
    def lock_guard(self): 
        pass


import threading


class LockedByteBuffer(ILockableByteBuffer):

    class LockGuard(ILockGuard):
        def __init__(self, lock_byte_buffer):
            super().__init__(lock_byte_buffer)
        
        def memoryview(self):
            return self.source()._internal().memoryview()
        def skip_bytes(self, size):
            return self.source()._internal().skip_bytes(size)

        def skip_all(self):
            return self.source()._internal().skip_all()

        def acquire(self):
            self.source().get_lock().acquire()

        def release(self):
            self.source().get_lock().release()



    def __init__(self, byte_buffer):
        self.__byte_buffer = byte_buffer
        self.__lock = threading.Lock()

    def get_lock(self):
        return self.__lock
    def _internal(self):
        return self.__byte_buffer


    def write_bytes(self, bytes_data):
        with self.__lock:
            return self.__byte_buffer.write_bytes(bytes_data)

    def read_bytes(self, size):
        with self.__lock:
            return self.__byte_buffer.read_bytes(size)

    def read_all(self):
        with self.__lock:
            return self.__byte_buffer.read_bytes(self.__byte_buffer.readable_bytes())
            

    def memoryview(self):
        with self.__lock:
            return self.__byte_buffer.view()

    def lock_guard(self):
        return LockedByteBuffer.LockGuard(self)


    def skip_bytes(self, size):
        with self.__lock:
            return self.__byte_buffer.skip_bytes(size) 
    def capacity(self):
        with self.__lock:
            return self.__byte_buffer.capacity()
        pass

    def readable_bytes(self):
        with self.__lock:
            return self.__byte_buffer.readable_bytes()
    def writable_bytes(self):
        with self.__lock:
            return self.__byte_buffer.writable_bytes()
    def is_readable(self):
        with self.__lock:
            return self.__byte_buffer.is_readable()
    def is_readable_size(self, size):
        with self.__lock:
            return self.__byte_buffer.is_readable_size(size)

    def is_writable(self):
        with self.__lock:
            return self.__byte_buffer.is_writable()
    def is_writable_size(self, size):
        with self.__lock:
            return self.__byte_buffer.is_writable_size(size)
        
    def clear(self):
        with self.__lock:
            self.__byte_buffer.clear()

    def reader_index(self):
        with self.__lock:
            return self.__byte_buffer.reader_index()

    def writer_index(self):
        with self.__lock:
            return self.__byte_buffer.writer_index()

    def _str_internal(self):
        return self.__byte_buffer._str_internal()

    def __str__(self):
        with self.__lock:
            return "LockedByteBuffer {}".format(self.__byte_buffer._str_internal())
    def __len__(self):
        return self.capacity()





import unittest


class TestBuffer(unittest.TestCase):
    @unittest.skip("skip")
    def test_locked_view(self):
        lbb = LockedByteBuffer(ByteBuffer())
        lbb.write_bytes(b"1234567890") #lock

        v = lbb.lock_guard()
        for e in v.memoryview():
            print(e)

        v.skip_bytes(len(v.memoryview()))

    def test_with_lock_view(self):
        lbb = LockedByteBuffer(ByteBuffer())
        lbb.write_bytes(b"1234567890") #lock

        with lbb.lock_guard() as v:
            for e in v.memoryview():
                print(e)

            v.skip_bytes(len(v.memoryview()))

    
    @unittest.skip("skip")
    def test_main(self):
        bb = ByteBuffer()

        bb.is_readable() #False

        bdata = b"1234567890"
        if bb.is_writable():
            self.assertEqual(len(bdata), bb.write_bytes(bdata))

        



        if bb.is_readable():
            self.assertEqual(bdata, bb.read_bytes(16))


        if bb.is_writable_size(len(bdata)):
            self.assertEqual(len(bdata), bb.write_bytes(bdata))

        if bb.is_readable():
            self.assertEqual(bdata, bb.read_all())

        bb.clear()


    @unittest.skip("skip")
    def test_details(self):
        self.assertEqual(16, ByteBuffer.calc_alloc_size(-1))
        self.assertEqual(16, ByteBuffer.calc_alloc_size(0))
        self.assertEqual(16, ByteBuffer.calc_alloc_size(8))
        self.assertEqual(16, ByteBuffer.calc_alloc_size(7))
        self.assertEqual(16, ByteBuffer.calc_alloc_size(16))
        self.assertEqual(32, ByteBuffer.calc_alloc_size(17))
        self.assertEqual(64, ByteBuffer.calc_alloc_size(33))

        self.assertEqual(512, ByteBuffer.calc_alloc_size(511))
        self.assertEqual(1024, ByteBuffer.calc_alloc_size(1022))

        self.assertEqual(1024 * 1024, ByteBuffer.calc_alloc_size(1024 * 1024))
        self.assertEqual(1024 * 1024 + 1024, ByteBuffer.calc_alloc_size(1024 * 1024 + 1))
        self.assertEqual(1024 * 1024 + 1024, ByteBuffer.calc_alloc_size(1024 * 1024 + 1024))
        self.assertEqual(1024 * 1024 + 1024 * 2, ByteBuffer.calc_alloc_size(1024 * 1024 + 1024 + 1))

        b = ByteBuffer()
        self.assertEqual(16, len(b))
        b = ByteBuffer(1)
        self.assertEqual(16, len(b))
        b = ByteBuffer(15)
        self.assertEqual(16, len(b))
        b = ByteBuffer(16)
        self.assertEqual(16, len(b))

        b = ByteBuffer()
        b.write_bytes(b'1024' * 1023)
        self.assertEqual(4 * 1024, len(b))

        b = ByteBuffer()
        b.write_bytes(b'1' * (1024 * 1024))
        self.assertEqual(1024 * 1024, len(b))

    @unittest.skip("skip")
    def test_write_speed(self):
        import time
        start = time.monotonic_ns()
        data = b'1' * (1024 * 1024 + 1)
        end = time.monotonic_ns()
        print("data span:{}".format(end - start))

        b = ByteBuffer()
        start = time.monotonic_ns()
        b.write_bytes(data)
        end = time.monotonic_ns()
        print("write data span:{}".format(end - start))

        self.assertEqual(1024 * 1024 + 1024, b.capacity())
        self.assertEqual(1024 * 1024 + 1024, len(b))
        self.assertEqual(len(data), b.readable_bytes())

        obj = b.read_bytes(1)
        self.assertEqual(1, len(obj))

        obj = b.read_bytes(1024 * 1024 + 100)
        self.assertEqual(1024 * 1024, len(obj))
        self.assertEqual(0, b.readable_bytes())
        self.assertFalse(b.is_readable())
        self.assertEqual(1024 * 1024 + 1, b.reader_index())
        self.assertEqual(1024 * 1024 + 1, b.writer_index())
        self.assertEqual(1024 * 1024 + 1024, b.capacity())
        print(b)

    @unittest.skip("skip")
    def test_move_inside_buffer(self):
        obj = ByteBuffer()
        obj.write_bytes(b'0123456789')
        self.assertEqual(0, obj.reader_index())
        self.assertEqual(10, obj.writer_index())
            
        data = obj.read_bytes(1)
        self.assertEqual(1, len(data))
        self.assertEqual(0x30, data[0])

        self.assertEqual(7, obj.writable_bytes())
        self.assertEqual(7, obj.write_bytes(b'0123456'))

#TODO: class TestLockedByteBuffer(unittest.TestCase): pass


"""
    vim cli :    !pytype %   -> check the code error 
                 !python %   -> run the unittest 
"""
if __name__ == "__main__":
    unittest.main(verbosity=2)
